package com.open.source.javassist;

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.NotFoundException;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.AttributeInfo;
import javassist.bytecode.ConstPool;
import javassist.bytecode.FieldInfo;

/**
 * @author ZonLen since on 2022/3/24 上午12:15
 */
public final class FieldMaker {

  private ModifierAccess modifierAccess = ModifierAccess.PRIVATE;

  private final ClassPool classPool;

  private final CtClass ctClass;

  private String filedName;

  private Class<?> filedType;

  private boolean isGenerateGet;

  private boolean isGenerateSet;

  private final List<AnnotationsAttribute> annotations = new ArrayList<>();

  private FieldMaker(ClassPool classPool, CtClass ctClass) {
    this.classPool = classPool;
    this.ctClass = ctClass;
  }

  public static FieldMaker instance(ClassPool classPool, CtClass ctClass) {
    return new FieldMaker(classPool, ctClass);
  }

  public FieldMaker modifierAccess(ModifierAccess modifierAccess) {
    this.modifierAccess = modifierAccess;
    return this;
  }

  public FieldMaker filedType(Class<?> filedType) {
    this.filedType = filedType;
    return this;
  }

  public FieldMaker filedName(String filedName) {
    this.filedName = filedName;
    return this;
  }

  public FieldMaker _annotations(Annotation[] annotationArr,
      BiFunction<Annotation, AnnotationMaker, AnnotationsAttribute> function) {
    if (annotationArr == null) {
      return this;
    }
    final ConstPool constPool = ctClass.getClassFile().getConstPool();
    for (Annotation annotation : annotationArr) {
      final AnnotationMaker maker = AnnotationMaker.instance(constPool);
      annotations.add(function.apply(annotation, maker));
    }
    return this;
  }

  public FieldMaker _annotation(Function<AnnotationMaker, AnnotationsAttribute> function) {
    final AnnotationMaker annotation = AnnotationMaker
        .instance(ctClass.getClassFile().getConstPool());
    this.annotations.add(function.apply(annotation));
    return this;
  }

  public FieldMaker generateGet() {
    this.isGenerateGet = true;
    return this;
  }

  public FieldMaker generateSet() {
    this.isGenerateSet = true;
    return this;
  }

  public CtField buildField() {
    CtField field;
    try {
      field = new CtField(classPool.get(filedType.getCanonicalName()), filedName, ctClass);
      field.setModifiers(modifierAccess.modifierAccess());
      field.getFieldInfo2().getAttributes().addAll(annotations);
      if (isGenerateGet) {
        ctClass.addMethod(newGetMethod(field));
      }
      if (isGenerateSet) {
        ctClass.addMethod(newSetMethod(field));
      }
    } catch (CannotCompileException | NotFoundException e) {
      throw new RuntimeException(e);
    }
    return field;
  }

  private CtMethod newSetMethod(CtField field)
      throws CannotCompileException {
    final String firstCharUpCase = filedName.substring(0, 1).toUpperCase() + filedName.substring(1);
    return CtNewMethod.setter("set" + firstCharUpCase, field);
  }

  private CtMethod newGetMethod(CtField field)
      throws CannotCompileException {
    final String firstCharUpCase = filedName.substring(0, 1).toUpperCase() + filedName.substring(1);
    return CtNewMethod.getter("get" + firstCharUpCase, field);
  }

}
